/*----------------------------------------------------------------------------
LZSS.C -- A Data Compression Program
4/6/1989 Haruhiko Okumura
Use, distribute, and modify this program freely.
Please send me your improved versions.
  PC-VAN  SCIENCE
  NIFTY-Serve PAF01022
  CompuServe 74050,1022

Some changes made January, 2024 by Ethan Zheng
  <zchhacker@163.com>
- Change reading text to reading character arrays
  Memory compression for adapting to embedded systems
- Remove the incoming parameters and use include text data
  Text memory contains arrays that need to be compressed
  Array data file: hex_data.c,hexdata.h
  Char array name: hexdataBlk0
  Output compress file name: compress.hex
  Output uncompress file name: uncompress.hex
- Change the original one run compression or decompression to one run compression and decompression
  For faster development and validation, if only one is needed, it can be manually adjusted
- Add macros for debugging and whether to output text
  GDB_DEBUG_ENABLE
  OUTPUT_COMPRESS_FILE
  OUTPUT_UNCOMPRESS_FILE
- Consistency check of added uncompressed data and decompressed data
  Used to confirm if the program is running correctly
- Adjust to external interface and add variable initialization
  Easy for external calls and repeated use
----------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "LZSS.h"

#define EI 11             /* typically 10..13 */
#define EJ 4              /* typically 4..5 */
#define P 1               /* If match length <= P then output one character */
#define N (1 << EI)       /* buffer size */
#define F ((1 << EJ) + 1) /* lookahead buffer size */

static unsigned char bit_buffer = 0, bit_mask = 128;
static unsigned long codecount = 0, textcount = 0;
static unsigned char buffer[N * 2] = {0};

static unsigned int compress_write_index = 0;
static unsigned int compress_read_index = 0;
static unsigned int uncompress_write_index = 0;

#if (LZSS_DATA_TO_FLASH)
// Whether to enable flashing while running
unsigned char LZSS_DATA_TO_FLASH_ENABLE = 0;
unsigned char LZSS_DATA_TO_FLASH_BUFFER[LZSS_DATA_TO_FLASH_SIZE] = {0};
unsigned char LZSS_DATA_TO_FLASH_COUNTER = 0;
static unsigned char LZSS_DATA_TO_FLASH_BUSY = 0; // Lock (ZCH adds code)
unsigned char LZSS_FLASH_PROGRAM_ERROR = FL_OK;

FL_ResultType LZSS_UnCompressFlashToRam_Program(void) //AppBackUp (ZCH adds code)
{
    FL_ResultType ret = FL_OK;

    if (LZSS_DATA_TO_FLASH_BUSY == 1)
    {
        ret = FL_FAILED;
    }
    else
    {
        LZSS_DATA_TO_FLASH_BUSY = 1;

        /* uncompress flash driver to RAM */
        LZSS_UnCompress(flashDrvBlk0, FLASHDRV_BLOCK0_LENGTH, (uint8 *)FL_FLASH_BASE_ADDRESS, 0x12000);

        LZSS_DATA_TO_FLASH_BUSY = 0;
    }

    return ret;
}

FL_ResultType LZSS_AppBackUp_Program(void) //AppBackUp (ZCH adds code)
{
    FL_ResultType ret = FL_OK;

    // Check if backup is required
    if ((*(volatile uint32 *)FL_BlkInfo[0].address) != 0x00)
    {
        if (LZSS_DATA_TO_FLASH_BUSY == 1)
        {
            ret = FL_FAILED;
        }
        else
        {
            LZSS_DATA_TO_FLASH_BUSY = 1;
            LZSS_FLASH_PROGRAM_ERROR = FL_OK;

            // Compress
            LZSS_DATA_TO_FLASH_ENABLE = 1;
            LZSS_Compress((uint8 *)FL_BlkInfo[0].address,FL_BlkInfo[0].length,LZSS_DATA_TO_FLASH_BUFFER,LZSS_DATA_TO_FLASH_SIZE);
            memset(LZSS_DATA_TO_FLASH_BUFFER, 0x00, LZSS_DATA_TO_FLASH_SIZE);
            LZSS_DATA_TO_FLASH_ENABLE = 0;

            // Record data length
            ret = FL_AppBackUp_Length(&codecount,FBL_31_LZSSCompressAppBackUpDataLength_Length);
            if (ret == FL_OK)
            {
                ret = LZSS_FLASH_PROGRAM_ERROR;
            }

            LZSS_DATA_TO_FLASH_BUSY = 0;
        }
    }

    return ret;
}

FL_ResultType LZSS_App_Program(void)
{
    FL_ResultType ret = FL_OK;
    unsigned long read_AppBackUpDataLength = 0;

    if (LZSS_DATA_TO_FLASH_BUSY == 1)
    {
        ret = FL_FAILED;
    }
    else
    {
        LZSS_DATA_TO_FLASH_BUSY = 1;
        LZSS_FLASH_PROGRAM_ERROR = FL_OK;
        Appl_Memcpy((uint8 *)&read_AppBackUpDataLength, (uint8 *)FBL_31_LZSSCompressAppBackUpDataLength_ADDR, FBL_31_LZSSCompressAppBackUpDataLength_Length);

        //Uncompress
        LZSS_DATA_TO_FLASH_ENABLE = 1;
        LZSS_UnCompress((uint8 *)FL_APP_BACKUP_ADDRESS,read_AppBackUpDataLength,LZSS_DATA_TO_FLASH_BUFFER,LZSS_DATA_TO_FLASH_SIZE);
        memset(LZSS_DATA_TO_FLASH_BUFFER, 0x00, LZSS_DATA_TO_FLASH_SIZE);
        LZSS_DATA_TO_FLASH_ENABLE = 0;

        ret = LZSS_FLASH_PROGRAM_ERROR;
        LZSS_DATA_TO_FLASH_BUSY = 0;
    }

    return ret;
}
#endif

static void error(void)
{
}

static void putbit1(unsigned char *output_array, unsigned int output_array_length)
{
    bit_buffer |= bit_mask;
    if ((bit_mask >>= 1) == 0)
    {
        if (compress_write_index < output_array_length)
        {            
            output_array[compress_write_index] = bit_buffer;
            compress_write_index++;
#if (LZSS_DATA_TO_FLASH)
            if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (compress_write_index == output_array_length))
            {
                FL_AppBackUp_Program(FL_APP_BACKUP_ADDRESS + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                    output_array,output_array_length);
                LZSS_DATA_TO_FLASH_COUNTER++;
                memset(output_array,0x00,output_array_length);
                compress_write_index = 0;
            }
#endif
        }
        else
        {
            error();
        }
        bit_buffer = 0;
        bit_mask = 128;
        codecount++;
    }
}

static void putbit0(unsigned char *output_array, unsigned int output_array_length)
{
    if ((bit_mask >>= 1) == 0)
    {
        if (compress_write_index < output_array_length)
        {
            output_array[compress_write_index] = bit_buffer;
            compress_write_index++;
#if (LZSS_DATA_TO_FLASH)
            if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (compress_write_index == output_array_length))
            {
                FL_AppBackUp_Program(FL_APP_BACKUP_ADDRESS + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                    output_array,output_array_length);
                LZSS_DATA_TO_FLASH_COUNTER++;
                memset(output_array,0x00,output_array_length);
                compress_write_index = 0;
            }
#endif
        }
        else
        {
            error();
        }
        bit_buffer = 0;
        bit_mask = 128;
        codecount++;
    }
}

static void flush_bit_buffer(unsigned char *output_array, unsigned int output_array_length)
{
    if (bit_mask != 128)
    {
        if (compress_write_index < output_array_length)
        {
            output_array[compress_write_index] = bit_buffer;
            compress_write_index++;
#if (LZSS_DATA_TO_FLASH)
            if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (compress_write_index == output_array_length))
            {
                FL_AppBackUp_Program(FL_APP_BACKUP_ADDRESS + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                    output_array,output_array_length);
                LZSS_DATA_TO_FLASH_COUNTER++;
                memset(output_array,0x00,output_array_length);
                compress_write_index = 0;
            }
#endif
        }
        else
        {
            error();
        }
        codecount++;
    }
#if (LZSS_DATA_TO_FLASH)
    if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (compress_write_index > 0))
    {
        // Record end of line data
        FL_AppBackUp_Program(FL_APP_BACKUP_ADDRESS + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                    output_array,compress_write_index);
        LZSS_DATA_TO_FLASH_COUNTER = 0;
        memset(output_array,0x00,output_array_length);
        compress_write_index = 0;
    }
#endif
}

static void output1(int c, unsigned char *output_array, unsigned int output_array_length)
{
    int mask;

    putbit1(output_array, output_array_length);
    mask = 256;
    while (mask >>= 1)
    {
        if (c & mask)
            putbit1(output_array, output_array_length);
        else
            putbit0(output_array, output_array_length);
    }
}

static void output2(int x, int y, unsigned char *output_array, unsigned int output_array_length)
{
    int mask;

    putbit0(output_array, output_array_length);
    mask = N;
    while (mask >>= 1)
    {
        if (x & mask)
            putbit1(output_array, output_array_length);
        else
            putbit0(output_array, output_array_length);
    }
    mask = (1 << EJ);
    while (mask >>= 1)
    {
        if (y & mask)
            putbit1(output_array, output_array_length);
        else
            putbit0(output_array, output_array_length);
    }
}

static void encode(unsigned char *input_array, unsigned int input_array_length, unsigned char *output_array, unsigned int output_array_length)
{
    int i, j, f1, x, y, r, s, bufferend, c;
    unsigned int hex_read_index = 0;

    for (i = 0; i < N - F; i++)
        buffer[i] = ' ';
    for (i = N - F; i < N * 2; i++)
    {
        if (hex_read_index < input_array_length)
        {
            c = input_array[hex_read_index];
            hex_read_index++;
        }
        else
        {
            break;
        }
        buffer[i] = c;
        textcount++;
    }
    bufferend = i;
    r = N - F;
    s = 0;
    while (r < bufferend)
    {
        f1 = (F <= bufferend - r) ? F : bufferend - r;
        x = 0;
        y = 1;
        c = buffer[r];
        for (i = r - 1; i >= s; i--)
            if (buffer[i] == c)
            {
                for (j = 1; j < f1; j++)
                    if (buffer[i + j] != buffer[r + j])
                        break;
                if (j > y)
                {
                    x = i;
                    y = j;
                }
            }
        if (y <= P)
        {
            y = 1;
            output1(c, output_array, output_array_length);
        }
        else
            output2(x & (N - 1), y - 2, output_array, output_array_length);
        r += y;
        s += y;
        if (r >= N * 2 - F)
        {
#if (LZSS_DATA_TO_FLASH)
            Wdg_17_Scu_SetTriggerCondition(5000); //WDG Clear Timer
#endif
            for (i = 0; i < N; i++)
                buffer[i] = buffer[i + N];
            bufferend -= N;
            r -= N;
            s -= N;
            while (bufferend < N * 2)
            {
                if (hex_read_index < input_array_length)
                {
                    c = input_array[hex_read_index];
                    hex_read_index++;
                }
                else
                {
                    break;
                }
                buffer[bufferend++] = c;
                textcount++;
            }
        }
    }
    flush_bit_buffer(output_array, output_array_length);
}

static int getbit(int n, unsigned char *input_array, unsigned int input_array_length) /* get n bits */
{
    int i, x;
    static int buf, mask = 0;

    x = 0;
    for (i = 0; i < n; i++)
    {
        if (mask == 0)
        {
            if (compress_read_index < input_array_length)
            {
                buf = input_array[compress_read_index];
                compress_read_index++;
            }
            else
            {
                return EOF;
            }
            mask = 128;
        }
        x <<= 1;
        if (buf & mask)
            x++;
        mask >>= 1;
    }
    return x;
}

static void decode(unsigned char *input_array, unsigned int input_array_length, unsigned char *output_array, unsigned int output_array_length)
{
    int i, j, k, r, c;

    for (i = 0; i < N - F; i++)
        buffer[i] = ' ';
    r = N - F;
    while ((c = getbit(1, input_array, input_array_length)) != EOF)
    {
        if (c)
        {
            if ((c = getbit(8, input_array, input_array_length)) == EOF)
                break;
            if (uncompress_write_index < output_array_length)
            {
                output_array[uncompress_write_index] = c;
                uncompress_write_index++;
#if (LZSS_DATA_TO_FLASH)
                if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (uncompress_write_index == output_array_length))
                {
                    FL_AppBackUp_Program(FL_BlkInfo[0].address + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                        output_array,output_array_length);
                    LZSS_DATA_TO_FLASH_COUNTER++;
                    memset(output_array,0x00,output_array_length);
                    uncompress_write_index = 0;
                }
#endif
            }
            else
            {
                error();
            }
            buffer[r++] = c;
            r &= (N - 1);
        }
        else
        {
            if ((i = getbit(EI, input_array, input_array_length)) == EOF)
                break;
            if ((j = getbit(EJ, input_array, input_array_length)) == EOF)
                break;
            for (k = 0; k <= j + 1; k++)
            {
                c = buffer[(i + k) & (N - 1)];
                if (uncompress_write_index < output_array_length)
                {
                    output_array[uncompress_write_index] = c;
                    uncompress_write_index++;
#if (LZSS_DATA_TO_FLASH)
                    if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (uncompress_write_index == output_array_length))
                    {
                        FL_AppBackUp_Program(FL_BlkInfo[0].address + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                                            output_array,output_array_length);
                        LZSS_DATA_TO_FLASH_COUNTER++;
                        memset(output_array,0x00,output_array_length);
                        uncompress_write_index = 0;
                    }
#endif
                }
                else
                {
                    error();
                }
                buffer[r++] = c;
                r &= (N - 1);
            }
        }
    }
#if (LZSS_DATA_TO_FLASH)
    if ((LZSS_DATA_TO_FLASH_ENABLE == 1) && (uncompress_write_index > 0))
    {
        // Record end of line data
        FL_AppBackUp_Program(FL_BlkInfo[0].address + LZSS_DATA_TO_FLASH_COUNTER*output_array_length,\
                            output_array,uncompress_write_index);
        LZSS_DATA_TO_FLASH_COUNTER = 0;
        memset(output_array,0x00,output_array_length);
        uncompress_write_index = 0;
    }
#endif
}

void LZSS_Compress(unsigned char *input_array, unsigned int input_array_length, unsigned char *output_array, unsigned int output_array_length)
{
    /* compress */
    bit_buffer = 0, bit_mask = 128;
    codecount = 0, textcount = 0;
    compress_write_index = 0;
    memset(buffer, 0x00, sizeof(buffer));
    memset(output_array, 0x00, output_array_length);
    encode(input_array, input_array_length, output_array, output_array_length);
    memset(buffer, 0x00, sizeof(buffer));
}

void LZSS_UnCompress(unsigned char *input_array, unsigned int input_array_length, unsigned char *output_array, unsigned int output_array_length)
{
    /* uncompress */
    uncompress_write_index = 0;
    compress_read_index = 0;
    memset(output_array, 0x00, output_array_length);
    decode(input_array, input_array_length, output_array, output_array_length);
}
